package org.pushforward.iwillsurvive.helpers;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.pushforward.iwillsurvive.data.DatabaseManager;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * Servlet implementation class XMLDump
 */
@WebServlet(description = "Converts the whole database to xml", urlPatterns = { "/XMLDump" })
public class XMLDump extends HttpServlet
{
	private static final long serialVersionUID = 1L;
	private static String XMLDir = "resources/xml/";
	private static File xmlDumpFile = null;
	private static boolean refresh = true;
	
	DatabaseManager db = DatabaseManager.getInstance();
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public XMLDump()
    {
        super();
    }
	
	/**
	 * Reads the list of users off the database and inserts them in XML
	 * @param xmlDump
	 */
	private boolean ReadUsers(PageBuilder xmlDump)
	{
		String[] getAllUsersStatement = {"select * from users;"};
		StringBuilder allUsersResult = new StringBuilder();
		
		if (!db.ExecuteStatements(getAllUsersStatement, db.GetEmptyParameters(1), allUsersResult))
		{
			return false;
		}
		
		allUsersResult.setLength(allUsersResult.length() - 1);
		String[] users = allUsersResult.toString().split("\n");

		allUsersResult.setLength(0);
		
		for (String user : users)
		{
			String[] userFields = user.split(",");
			
			allUsersResult.append(
					PageBuilder.CreateDeepElement(2, "user",
							PageBuilder.CreateDeepElement(3, "email", userFields[0]) +
							PageBuilder.CreateDeepElement(3, "name", userFields[2]) +
							PageBuilder.CreateDeepElement(3, "photosrc", userFields[3])
					)
			);
		}
		
		try
		{
			xmlDump.AddBodyElement("users", allUsersResult.toString());
		}
		catch (EOFException e)
		{
			System.err.println(e.getMessage());
			e.printStackTrace();
			return false;
		}
		
		return true;
	}

	/**
	 * Reads the list of posts off the database and inserts them in XML
	 * @param xmlDump
	 */
	private boolean ReadPosts(PageBuilder xmlDump)
	{
		String[] getAllPostsStatement = {"select * from posts;"};
		StringBuilder allPostsResult = new StringBuilder();
		
		if (!db.ExecuteStatements(getAllPostsStatement, db.GetEmptyParameters(1), allPostsResult))
		{
			return false;
		}
		
		allPostsResult.setLength(allPostsResult.length() - 1);
		String[] posts = allPostsResult.toString().split("\n");

		allPostsResult.setLength(0);
		
		for (String post : posts)
		{
			String[] postFields = post.split(",");
			String[] coordinates = postFields[3].split(" ");
			
			allPostsResult.append(
					PageBuilder.CreateDeepElement(2, "post",
							PageBuilder.CreateDeepElement(3, "id", postFields[0]) +
							PageBuilder.CreateDeepElement(3, "creator_email", postFields[1]) +
							PageBuilder.CreateDeepElement(3, "type", (postFields[2].equalsIgnoreCase("0")) ? "Task" : "Team") +
							PageBuilder.CreateDeepElement(3, "point", 
									PageBuilder.CreateDeepElement(4, "coordinates", coordinates[1] + "," + coordinates[0] + ",0")) +
							PageBuilder.CreateDeepElement(3, "creation_time", postFields[4]) +
							PageBuilder.CreateDeepElement(3, "end_time", postFields[5]) +
							PageBuilder.CreateDeepElement(3, "capacity", postFields[6]) +
							PageBuilder.CreateDeepElement(3, "abilities", postFields[7].replaceAll("@", ", ")) +
							PageBuilder.CreateDeepElement(3, "users_limit", postFields[8]) +
							PageBuilder.CreateDeepElement(3, "users", postFields[9].replaceAll("@", ", ")) +
							PageBuilder.CreateDeepElement(3, "content", ((postFields[10].equalsIgnoreCase("null")) ? 
																			"" : postFields[10]))
					)
			);
		}
		
		try
		{
			xmlDump.AddBodyElement("posts", allPostsResult.toString());
		}
		catch (EOFException e)
		{
			System.err.println(e.getMessage());
			e.printStackTrace();
			return false;
		}
		
		return true;
	}

	/**
	 * Reads the list of geodesics off the database and inserts them in XML
	 * @param xmlDump
	 */
	private boolean ReadGeodesics(PageBuilder xmlDump)
	{
		String[] getAllGeodesicsStatement = {"select * from geodesics;"};
		StringBuilder allGeodesicsResult = new StringBuilder();
		
		if (!db.ExecuteStatements(getAllGeodesicsStatement, db.GetEmptyParameters(1), allGeodesicsResult))
		{
			return false;
		}
		
		allGeodesicsResult.setLength(allGeodesicsResult.length() - 1);
		String[] geodesics = allGeodesicsResult.toString().split("\n");

		allGeodesicsResult.setLength(0);
		
		for (String geodesic : geodesics)
		{
			String[] geodesicFields = geodesic.split(",");
			String[] task_coordinates = geodesicFields[1].split(" ");
			String[] team_coordinates = geodesicFields[3].split(" ");
			
			allGeodesicsResult.append(
					PageBuilder.CreateDeepElement(2, "geodesic",
						PageBuilder.CreateDeepElement(3, "task", geodesicFields[0]) +
						PageBuilder.CreateDeepElement(3, "task_coordinates", task_coordinates[1] + "," + task_coordinates[0] + ",1000") +
						PageBuilder.CreateDeepElement(3, "team", geodesicFields[2]) +
						PageBuilder.CreateDeepElement(3, "team_coordinates", team_coordinates[1] + "," + team_coordinates[0] + ",1000")
					)
			);
		}
		
		try
		{
			xmlDump.AddBodyElement("geodesics", allGeodesicsResult.toString());
		}
		catch (EOFException e)
		{
			System.err.println(e.getMessage());
			e.printStackTrace();
			return false;
		}
		
		return true;
	}

	synchronized private boolean WriteXMLFromDatabase(HttpServletResponse response) throws IOException
	{
		PageBuilder xmlDump = new PageBuilder(response).SwitchToXML();
		boolean success = true;
		
		success = success && ReadUsers(xmlDump);
		success = success && ReadPosts(xmlDump);
		success = success && ReadGeodesics(xmlDump);
		
		xmlDump.CloseBody();
		if (!xmlDump.PrepareXML())
		{
			return false;
		}
		
		synchronized (xmlDumpFile)
		{
			if (xmlDumpFile.exists())
			{
				xmlDumpFile.delete();
			}
			
			if (!xmlDumpFile.exists())
			{
				xmlDumpFile.createNewFile();
			}

			if (xmlDumpFile.canWrite())
			{
				FileWriter fileWriter = new FileWriter(xmlDumpFile);
				fileWriter.write(xmlDump.toString());
				fileWriter.close();
				return success;
			}
			else
			{
				response.setStatus(500);
				return false;
			}
		}
	}

	/**
	 * Makes sure the Database XML and the required KML are up to date
	 * 
	 * @param response The response to send back. Needed for the PageBuilder
	 * @param XSLFile The file reference for the relevant XSL
	 * @param outputKML The KML to output to
	 * @throws IOException
	 */
	private boolean UpdateFiles(HttpServletResponse response, File XSLFile, File outputKML)
			throws IOException
	{
		try
		{
			// write a new database xml
			WriteXMLFromDatabase(response);

			// and output it to a new KML
			DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document XMLDoc = documentBuilder.parse(xmlDumpFile);
			Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(XSLFile));
			if (outputKML.exists())
			{
				outputKML.delete();
			}
			outputKML.createNewFile();
			Result transformResult = new StreamResult(outputKML);
			transformer.transform(new DOMSource(XMLDoc), transformResult);

			// only set back refresh when file is written
			refresh = false;
			return true;
		} 
		catch (IOException | ParserConfigurationException | SAXException | 
				TransformerFactoryConfigurationError | TransformerException e)
		{
			Utils.Log("XMLDump: Error in XML parsing and transforming - " + e.getMessage());
			System.err.println(e.getMessage());
			e.printStackTrace();
			response.setStatus(500);
			response.getWriter().write("error");
			return false;
		}
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
	{
		if (xmlDumpFile == null)
		{
			xmlDumpFile = new File(getServletContext().getRealPath(XMLDir + "xmldump.xml"));
		}
		// Check file request is correct
		String fileName = request.getParameter("file");
		if ((!fileName.equalsIgnoreCase("tasks")) &&
				(!fileName.equalsIgnoreCase("teams")) &&
				(!fileName.equalsIgnoreCase("geodesics")))
		{
			Utils.Log("XMLDump: Wrong file name requested.");
			throw new IOException("Wrong file name.");
		}

		// create file references
		File XSLFile = new File(getServletContext().getRealPath(XMLDir + fileName + ".xsl"));
		File outputKML = new File(getServletContext().getRealPath(XMLDir + fileName + ".kml"));

		// if the requested file doesn't exist or it needs to be refreshed
		if (refresh || !outputKML.exists())
		{
			synchronized (outputKML)
			{
				if (!UpdateFiles(response, XSLFile, outputKML))
				{
					return;
				}
			}
		}

		synchronized (outputKML)
		{
			if (Boolean.parseBoolean(request.getParameter("save")))
			{
				// set the response with the requested file
				response.setContentType("application/force-download");
				response.setContentLength((int)outputKML.length());
				response.setHeader("Content-Transfer-Encoding", "binary");
				response.setHeader("Content-Disposition","attachment; filename=\"" + fileName + ".kml\"");
	
				// write the file to the response
				PrintWriter writer = response.getWriter();
				BufferedReader fileReader = new BufferedReader(new FileReader(outputKML));
				StringBuilder responseBuilder = new StringBuilder();
				String line;
				while ((line = fileReader.readLine()) != null)
				{
					responseBuilder.append(line).append("\n");
				}
				fileReader.close();
				writer.write(responseBuilder.toString());
			}
			else
			{
				response.getWriter().write(DatabaseManager.appbase + "/" + XMLDir + fileName + ".kml");
			}
		}
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		String refreshString = request.getParameter("refresh");
		if (refreshString != null)
		{
			refresh = true;
		}
		else
		{
			response.sendRedirect("..401");
		}
	}

}
